[Kotlin][Android] Retrofit + KotlinでChatwork APIを叩く
こんにちは。Steam厨のこむろ@札幌です。ここ最近はPAYDAY2です。 *1
お盆を過ぎたら札幌はめっきり朝夕冷え込むようになりました。本当にエアコンはいらない土地でした(驚愕
今回は、弊社諏訪ネ申のRxAndroid+Retrofitの記事 にインスパイアされて書きました。
はじめに
ChatworkのREST APIをコールするAndroidアプリケーションを作成します。今回は、GET
と POST
を実装してみます。
Retrofit + Kotlin
基本的にはJavaの時とあまり変わりませんが、大きく違うことがあります。 null
を許容する値を大幅に駆逐できることです。
Retrofitを利用する際には、レスポンスJSONをマッピングするためのオブジェクトが必要になります。Kotlinでは以下のように記述することが出来ました。 破壊的代入を可能にする var
や null
を許容する ?
も書かずにオブジェクトを定義することができました。
この記事では、 チャットルーム一覧の取得 と メッセージ投稿 のAPIをコールするアプリを作成してみます。
REST APIを実行するための準備
Retrofitを利用してREST APIの呼び出しを定義するための準備をしましょう。
RestAdapterの作成
処理を呼び出したい箇所でRetrofitのRestAdapterを作成します。この辺りの実装は こちら を参考にしています。JsonParserにはGsonを利用します。
// chatwork API endpoint private val ENDPOINT = "https://api.chatwork.com/v1" val gsonBuilder = GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(javaClass(), DateTypeAdapter()).create() val restAdapter = RestAdapter.Builder() .setEndpoint(ENDPOINT) .setConverter(GsonConverter(gsonBuilder)) .setLogLevel(RestAdapter.LogLevel.FULL) .setLog(AndroidLog("=NETWORK=")) .build();
特に認証など必要ないAPIであればこれだけで問題ありません。 今回は、Chatwork APIを対象にしているため、認証情報をヘッダに埋め込む必要があります。
API TOKENをヘッダに埋め込む
ヘッダ情報に情報を追加する場合は、Retrofitでは RequestInterceptor
を利用します。
今回の場合はChatwork APIのAPI TOKENを埋め込むので以下のように記述します。
private val API_KEY = "" private val TOKEN_KEY = "X-ChatWorkToken" val requestInterceptor: RequestInterceptor = object: RequestInterceptor { override fun intercept(request: RequestInterceptor.RequestFacade?) { request?.let { it.addHeader(TOKEN_KEY, API_KEY) } } }
RequestInterceptor
にAPI TOKENを追加したら、先ほど定義したRestAdapterにセットします。
val restAdapter = RestAdapter.Builder() .setEndpoint(ENDPOINT) .setRequestInterceptor(requestInterceptor) .setConverter(GsonConverter(gsonBuilder)) .setLogLevel(RestAdapter.LogLevel.FULL) .setLog(AndroidLog("=NETWORK=")) .build();
これでChatwork APIをコールするための準備が整いました。
チャットルーム一覧を取得する
自分のチャット一覧取得 を確認してみるとAPIを GET
で引数なしで呼び出す必要があるようです。1つずつ準備をしていきましょう。
レスポンスをマッピングするオブジェクトを定義する
ChatRoom一覧APIは以下の様なJSONがレスポンスとして返却されます。
[ { "room_id": 123, "name": "Group Chat Name", "type": "group", "role": "admin", "sticky": false, "unread_num": 10, "mention_num": 1, "mytask_num": 0, "message_num": 122, "file_num": 10, "task_num": 17, "icon_path": "https://example.com/ico_group.png", "last_update_time": 1298905200 } ]
配列になっているオブジェクトをマッピングするための RoomEntityクラス を作成します。
public class RoomEntity(roomId: Int, name: String, type: String, role: String, sticky: Boolean, unreadNum: Int, mentionNum: Int, mytaskNum: Int, messageNum: Int, fileNum: Int, taskNum: Int, iconPath: String, lastUpdateTime: Long) { public val roomId: Int = roomId public val name: String = name public val type: String = type public val role:String = role public val sticky: Boolean = sticky public val unreadNum: Int = unreadNum public val mentionNum: Int = mentionNum public val mytaskNum: Int = mytaskNum public val messageNum: Int = messageNum public val fileNum: Int = fileNum public val taskNum: Int = taskNum public val iconPath: String = iconPath public val lastUpdateTime: Long = lastUpdateTime }
全て null
を許可しない値で定義してあるので、レスポンスを取得した後の処理では特にnullチェックをすることなく利用することが出来ます。
インターフェースの作成
続いてAPI呼び出しのインターフェースを作成します。
/** * Created by komurohiraku on 2015/07/12. */ public interface ChatworkService { @GET("/rooms") public fun getRooms(): Observable<Array> }
https://api.chatwork.com/v1/rooms
というパスに対して GET
を実行した場合の動作は getRooms()
という名前でチャットルーム一覧を取得するメソッドとして定義します。
返却値には先ほど作成した RoomEntity
の配列を Observable
に包んで指定します。 *2
チャットルーム一覧取得を実行する
表示する Fragment
内で チャットルーム一覧取得 を呼び出す実装を行います。
restAdapter.create(javaClass()) .getRooms() .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object:Observer<Array>{ override fun onCompleted() { // 正常に完了した場合 } override fun onError(e: Throwable?) { // エラーの場合 } override fun onNext(t: Array?) { // 受信した結果 } })
これでまずはチャットルーム一覧のAPIを呼び出すことができるようになりました。
チャットルーム一覧の結果を加工する
結果のレスポンスは override fun onNext(t: Array?)
で取得することが出来ます。
Chatwork APIのチャット一覧のレスポンスを確認すると分かるのですが、レスポンスにはチャットルームである グループ と 個人 が混ざっています。今回は、チャットルームの一覧を取得したいので、 グループ のみをフィルタして結果としましょう。
override fun onNext(t: Array?) { t?.let { val groups = it.filter { room -> room.type.equals("group") } // ランダムで一つの部屋を選択して通知 val index = Random().nextInt() * 100 % groups.size() val room = groups.get(Math.abs(index)) Toast.makeText(getActivity(), room.name + "が選択されました", Toast.LENGTH_SHORT).show() } }
Kotlinの Array
の filter
を利用して type
が "group" であるもののみを抽出します。抽出した結果もまた Array
です。
チャットルーム一覧取得の機能はこれで概ね完成。全体のコードは以下の通り。
restAdapter.create(javaClass()) .getRooms() .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object:Observer<Array>{ override fun onCompleted() { // 正常に完了した場合 Snackbar.make(getView(), "ネコと和解せよ!", Snackbar.LENGTH_SHORT).show() } override fun onError(e: Throwable?) { // エラーの場合 e?.printStackTrace() } override fun onNext(t: Array?) { // 受信した結果 t?.let { val groups = it.filter { rooms -> rooms.type.equals("group") } val index = Random().nextInt() * 100 % groups.size() val room = groups.get(Math.abs(index)) Toast.makeText(getActivity(), room.name + "が選択されました", Toast.LENGTH_SHORT).show() } } })
以上がチャットルーム一覧を取得する方法。
メッセージを投稿する
メッセージを投稿する機能を実装します。チャットに新しいメッセージを追加 こちらを確認すると、APIを POST
で呼び出し引数にメッセージとなる文字列を FormUrlEncode
に設定すれば良いようです。さっそく作っていきましょう。 *3
レスポンスをマッピングするオブジェクトを定義する
特定のチャットルームにメッセージを投稿する際のレスポンスは、メッセージIDのみが返却されます。
{ "message_id": 1234 }
そのため、レスポンスオブジェクトは ResponseEntity
として以下のように定義します。
public class ResponseEntity(messageId: Int) { public val messageId:Int = messageId }
messageId
のみで事足りるため特に問題はないでしょう。 *4
インターフェースの作成
メッセージを投稿する機能のインターフェースを作成します。
@FormUrlEncoded @POST("/rooms/{room_id}/messages") public fun postMessage(@Path("room_id") roomId:Int, @Field("body") message: String): Observable
@POST("/rooms/{room_id}/messages")
パスの中に {}
があります。このように記述するとメソッドの引数を使い、任意の値を埋め込む事ができます。
メソッドの引数でPathに任意の値を埋め込む
@Path("room_id") roomId:Int
この引数で指定された値がPathに埋め込まれて実行されます。
URLエンコードを指定する
@FormUrlEncoded
このアノテーションの指定と @Field("body")
を Body で指定された文字列に対して必要になります。
メッセージ投稿を実行する
先ほどと同じく Fragment
内に実装してみます。呼び出し方は先程のチャットルーム一覧取得と変わりません。異なるのは引数に情報が必要であることくらいでしょうか。
private fun postMessage(roomId:Int, message:String, observer: Observer): Subscription { return restAdapter.create(javaClass()) .postMessage(roomId, message) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer) }
一つのプライベートメソッドとして定義してみます。呼び出し方は以下の通り。
postMessage(13768115, "ネコと和解せよ!", object:Observer { override fun onCompleted() { // 正常に完了した場合 Toast.makeText(getActivity(), "投稿されましたにゃ", Toast.LENGTH_SHORT).show() } override fun onNext(t: ResponseEntity?) { // レスポンスを受信 t?.let { Log.d("PieceNeko", "MessageID : " + it.messageId) } } override fun onError(e: Throwable?) { // エラーの場合 e?.printStackTrace() } })
実行結果
実行した結果はこんな感じ
まとめ
Retrofitが便利そうだったのでKotlinで使ってみました。Kotlinらしいコードには程遠いですが、問題なく直感的にサクサク書けます。通信の結果を加工するあたりでは、Kotlinが大いに活躍できるのではないでしょうか?(RxAndroidと機能被る部分もありそうではありますが)
次はこれらの機能を組み合わせてみます。